package cc.verywell.pureblock.func;


import java.nio.ByteBuffer;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import cc.verywell.pureblock.func.FuncIndex.FuncIndexGenerator;
import cc.verywell.pureblock.func.node.Node;
import cc.verywell.pureblock.func.node.NodeBucket;
import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.net.Msg;
import cc.verywell.pureblock.net.NormalSession;
import cc.verywell.pureblock.util.SignServer;
import cc.verywell.pureblock.util.StringUtil;

/**
* 
* 
* 
* Copyright 2019 TanYaqiu
* @author TanYaqiu 
* @address flat 601
* @date 2019年3月16日 下午3:58:55
*/
public class FuncProviderManager implements FuncList{

	private FuncProvider[] provider = new FuncProvider[0];
	private FuncProvider[] providerSortCache = null;
	
	/**
	 * 只能添加，已经有的不能修改，不能减少；
	 * @param provider
	 */
	public  void addNew(FuncProvider... provider) {
		synchronized(this) {
			FuncProvider[] oldp = this.provider;
			if(oldp==null)throw new RuntimeException("manager already closed;");
			FuncProvider[] newp = new FuncProvider[oldp.length+provider.length];
			System.arraycopy(oldp, 0, newp, 0, oldp.length);
			System.arraycopy(provider, 0, newp, oldp.length, provider.length);
			this.provider = newp;
			this.providerSortCache = null;
			for (int i = oldp.length,j=0; j < provider.length; i++,j++) {
				provider[j].setIndex(i); //从0开始;
			}
		}
		for (FuncProvider f : provider) {
			if(f instanceof NodeBucket.NewKeyListener) {
				this.nodeBucket.addNewKeyListener((NodeBucket.NewKeyListener)f);
			}
		}
	}
	@Override
	public boolean hasFunc(FuncProvider provider) {
		FuncProvider[] oldp = this.provider;
		if(oldp==null)throw new RuntimeException("manager already closed;");
		int i = provider.getIndex();
		return (0<=i && i<oldp.length)?(oldp[i]==provider):false;
	}
	
	/**
	 * 获得当前的支持列表;
	 * @return
	 */
	public FuncProvider[] getProviders() {
		FuncProvider[] ps = providerSortCache;
		if(ps==null) {
			synchronized(this) {
				ps = providerSortCache;
				if(ps==null) {
					FuncProvider[] oldp = this.provider;
//					for (FuncProvider f : oldp) {
//						System.out.println(f);
//					}
					FuncProvider[] newp = Arrays.copyOf(oldp, oldp.length);
					///这个排序其实消耗挺大;
					Arrays.sort(newp, new Comparator<FuncProvider>() {
						@Override
						public int compare(FuncProvider o1, FuncProvider o2) {
							 int i = StringUtil.compare(o1.funcName(), o2.funcName());
							 if(i==0)throw new IllegalArgumentException("MSG provider "+o1.funcName()+" already exists.("+o1+" == "+o2+")");
							 return i;
						}
					});
					this.providerSortCache = ps = newp;
				}
			}
		}
		return ps;
	}
	
//	public static String[] getProviderNames(FuncProvider[] newp) {
////		String[] newn = this.providerNameCache;
////		if(newn==null) {
////			synchronized(this) {
////				newn = this.providerNameCache;
////				if(newn==null) {
////					FuncProvider[] newp = getProviders();
//					String[] newn = new String[newp.length];
//					for (int i = 0; i < newp.length; i++) {
//						newn[i] = newp[i].funcName();
//					}
////					this.providerNameCache = newn;
////				}
////			}
////		}
//		return newn;
//	}
	
	private byte[][] cachelists = new byte[0][];
//	static private byte[] indexlists = new byte[0];
	public FuncProviderList getFuncsInited(final NodeBucket.ValueShell node,String[] remoteProvider,FuncProvider[] sentProvider) {
		//FuncProvider[] ps = getProviders();
		FuncProvider[] newp = new FuncProvider[sentProvider.length>remoteProvider.length?remoteProvider.length:sentProvider.length];
		List<String> ns = new LinkedList<String>(Arrays.asList(remoteProvider));
		int i = 0;
		int maxIndex = 0;
		for (FuncProvider p : sentProvider) {
			String n = p.funcName();
			Iterator<String> it = ns.iterator();
			while(it.hasNext()) {
				if(n.equals(it.next())) {
					newp[i++] = p;
					maxIndex = maxIndex>p.getIndex()?maxIndex:p.getIndex();
					it.remove();
					break;
				}
			}
		}
		if(i!=newp.length) {
			newp = Arrays.copyOf(newp, i);
		}
		/////编号长度;
		int w=1,length = 1+newp.length; //那个+1是为了让indexlist加一个判断有还是没有;
		while(0!=(length >>>= 8))w++;
		/////为各自的位置编号;
		byte[] cl = null;
		//读取cl缓存;
		byte[][] cls  = cachelists;
		if(cls.length>w) {
			cl = cls[w];
			if(cl!=null && cl.length<w*i) {
				cl=null;
			}
		}
		if(cl==null) {
			//生成cl;
			ByteBuffer bb = ByteBuffer.allocate(w*sentProvider.length); //尽量大;
			i=0;
			while(bb.hasRemaining()) {
				StringUtil.writeLength(w, i++, bb);
			}
			cl = bb.array();
			//缓存cl;
			if(cls.length<=w) {
				byte[][] newcls = new byte[w+1][];
				System.arraycopy(cls, 0, newcls, 0, cls.length);
				cachelists = cls = newcls;
			}
			cls[w] = cl;
		}
		return new FuncProviderList(node, newp, cl, w, sentProvider.length==newp.length);
	}
	
	
//	public String funcName() {
//		return "name_version"; //length<256;
//	}
//	
//	@Override
//	public String toString() {
//		//用于出错时找原因;
//		return this.getClass().getName()+"$"+Integer.toHexString(this.hashCode());
//	}
//	
//	
//	Msg decode(ByteBuffer bb,NormalSession nc) {
//		return null;
//	}
//	void encode(Msg msg,NormalSession nc,ByteBuffer bb) {
//		
//	}
	
	////////////////////////////////////context义务开始//////////////////////////////
	
	final public NodeBucket nodeBucket;
	final private SignServer sign;
	public FuncProviderManager(SignServer sign) {
		this.sign = sign;
		nodeBucket = new NodeBucket(new SelfNode(sign.getKey().getEncoded()));
		if(null==nodeBucket.getMyNode().getPubKey())throw new NullPointerException();
	}
	
	private String[] bindPort;
	public synchronized void addBindPort(String... newBindPort) {
		int oldLength = bindPort!=null?bindPort.length:0;
		String[] newp = new String[oldLength+newBindPort.length];
		if(oldLength>0)System.arraycopy(bindPort, 0, newp, 0, oldLength);
		System.arraycopy(newBindPort, 0, newp, oldLength, newBindPort.length);
		bindPort = newp;
	}
	public synchronized String[] getBindPort() {
		return bindPort;
	}
	
	private class SelfNode extends Node{
		public SelfNode(byte[] pubKey) {
			this.pubKey = pubKey;
		}
		private ProviderNameBuffer providerNameBuffer;
		@Override
		public String[] getFuncs() {
			FuncProvider[] fp = FuncProviderManager.this.getProviders();
			ProviderNameBuffer nb = providerNameBuffer;
			if(nb!=null && nb.providers==fp)return nb.names;
			providerNameBuffer = nb = new ProviderNameBuffer(fp);
			return nb.names;
		}
		@Override
		public String[] getRemote() {
			return FuncProviderManager.this.getBindPort();
		}
		@Override
		public long getRemoteSentTime() {
			return System.currentTimeMillis();
		}
		private int start,end,limit;
		private ByteBuffer writingBuffer;
		private int sessionSalt;
		@Override
		public synchronized int getSalt() {
			return sessionSalt;
		}
		@Override
		public synchronized byte[] getSign() {
			if(writingBuffer==null)return null;
			end = writingBuffer.position();
			limit = writingBuffer.limit();
			writingBuffer.limit(end).position(start);
			byte[] s;
			try {
				s = FuncProviderManager.this.sign.sign(writingBuffer);
				if(Pblog.DEBUG)Pblog.DEBUG("signed node ",getNode());
			} catch (SignatureException e) {
				if(Pblog.SECURE)Pblog.SECURE(e);
				throw new RuntimeException(e);
			}
			writingBuffer.limit(limit).position(end);
			return s;
		}
		@Override
		public synchronized void write(ByteBuffer bb, NormalSession nc) {
			sessionSalt = nc.salt;
			start = bb.position();
			writingBuffer = bb;
			super.write(bb, nc);
			this.writingBuffer = null;
		}
	}
	private static class ProviderNameBuffer{
		final FuncProvider[] providers;
		final String[] names;
		public ProviderNameBuffer(FuncProvider[] providers) {
			this.providers = providers;
			if(providers==null)names = null;
			else {
				names = new String[providers.length];
				for (int i = 0; i < providers.length; i++) {
					names[i] = providers[i].funcName();
				}
			}
		}
	}
	
	
	/**
	 * 
	 * every time self provider update ,
	 * juse need NodeBucket.addNode(this.newSelfNode());
	 * then the local Node will updated; 
	 * 
	 * @return newSelfNode
	 */
	@Override
	public NodeBucket.ValueShell getNode() {
		return nodeBucket.getMyNode();
	}
	
	
	public void newSession(NormalSession nc) {
		//NormalSession那边还没有FuncProvider //nc.getFuncProvider().newSession(nc);
		FuncProvider[] fs = getProviders();
		FuncProviderInit init = new FuncProviderInit(fs,nodeBucket,this);//初始化用临时FuncProvider;
		nc.setFunc(init);
		//TODO pubKey and remote not ready;
		//InitMsg msg = new InitMsg(nodeBucket.getMyNode().getPubKey(), fs, getBindPort(), sign);
		//nc.writeMsg(msg);
		nc.writeMsg(getNode().getNode());
	}
	
	public void closeSession(NormalSession nc) {
		//if(null==nc.getFunc())return; //在newSession时就已经挂了,让它报错;
//		try {
			nc.getFunc().closeSession(nc);//再来一次就让它报错吧.
//		}finally {
//			nc.setFunc(null);
//		}
	}
	
	public synchronized void close(){
		for (FuncProvider f : provider) {
			try {
				f.close();
			} catch (Throwable e) {
				if(Pblog.IO)Pblog.IO(e);
			}
		}
		provider = null;
	}
	
	
	
	
	////////////////////////////////////context走廊开始////////////////////////////////
	
	
	/**
	 * 解包线程，这里出错会关掉channel;
	 * @param bb
	 * @return
	 */
	@Override
	public Msg decode(ByteBuffer bb, NormalSession nc) {
		FuncList fp = nc.getFunc();
		return fp.decode(bb, nc);//如果没有就直接报错了，然后就断开连接，这样很好;
	}

	/**
	 * 就是一条走廊，这里出错会ByteBuffer.clear,但不会关闭channel;
	 */
	@Override
	public void encode(Msg msg, NormalSession nc, ByteBuffer bb) {
		FuncList fp = nc.getFunc();
		fp.encode(msg, nc, bb);//如果没有就报错IllegalArgumentException，然后就把这个包会触发msg.failed，这样很好;
	}

//	@Override
//	public String funcName() {
//		return "__FuncProviderManager__";
//	}
	@Override
	public String toString() {
		//用于出错时找原因;
		return this.getClass().getName()+"$"+Integer.toHexString(this.hashCode());
	}
	
	///////////////////////////////////////////////
	
	/**
	 * 返回true 会暂停读包, 
	 * 可以通过 NormalSession.startRead()恢复读包;
	 * 留意TimeOut,TimeoutRunner依然会自动断开连接的;
	 * @param Msg msg
	 * @param NormalSession nc
	 * @return true PauseRead;
	 */
	public boolean toDo(Msg msg,NormalSession nc) {
		 //TODO 未来建了包要另外线程，别再占用IO线程了。然后排队太多了要阻塞一下，也由这边控制;
		return msg.getFunc().toDo(msg, nc);
	}
	public void sessionTimeout(NormalSession nc) {
		nc.getFunc().sessionTimeout(nc);
	}

	////////////////////////////////////////////////
	
	static public FuncIndexGenerator funcIndex = new FuncIndex.DefaultFuncIndexGenerator();

}
